事件循环是一个循环,只要 Node.js 应用程序正在运行,它就会一直运行。每个循环中有六个不同的队列,每个队列都包含一个或多个需要在调用堆栈上执行的回调函数。
下面这个配图展示了常见的异步回调方法。
看到上面的配图是不是有一点点蒙?不知道这些回调的执行顺序是怎样的?
当我们了解完 Node.js 的事件循环 (Event Loop) 规则之后就能够知道。
Node 与浏览器中的 Event Loop 完全不同,
Node 的 Event Loop 分为 6 个阶段,会按照顺序反复执行,每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。
setTimeout
和 setInterval
回调;I/O
回调,比如 fs
/http
模块执行的回调;I/O
事件;执行 I/O 相关的回调 (几乎是除关闭回调、定时器回调和 setImmediate 以外的回调);在适当的时候节点将阻塞在这里;setImmediate
回调 (Node.js 专有方法);socket.on('close', ...)
。JavaScript
中的原生 Promise 相关的回调函数。在 6 个阶段中并没有显示
process.nextTick
,虽然它也是异步的,因为从实现上来说他不是事件循环的一部分。在某一操作完成后,将处理nextTickQueue
,而不考虑事件循环的当前阶段。在给定阶段调用
process.nextTick()
时,在process.nextTick()
触发的所有回调执行之前,事件循环将会停顿。这可能会带来一些极端的情况,通过递归调用
process.nextTick()
而使 I/O 停止,可以阻止事件循环进入轮循阶段。
我们通过下面的例子先来简单看看结果。
下面是个较完整的包含相关阶段执行的例子,
```js import fs from 'fs'
process.on('exit', () => { console.log('11') })
// 异步读取文件 fs.readFile('./1.mjs', () => { console.log('10') })
setTimeout(() => { console.log('8') }, 0) setTimeout(() => { console.log('9') }, 0)
process.nextTick(() => { console.log('2') Promise.resolve().then(() => { console.log('5') }) process.nextTick(() => { console.log('3') process.nextTick(() => { console.log('4') }) }) })
setImmediate(() => { console.log('6') Promise.resolve().then(() => { console.log('7') }) })
console.log('1') ```
下面展开介绍一下执行规则。
毋庸置疑,先执行同步代码,紧接着才是回调部分。
nextTick队列
,然后 promise队列
。```js Promise.resolve().then(() => { console.log(4) })
process.nextTick(() => { console.log(2) Promise.resolve().then(() => { console.log(5) }) process.nextTick(() => { console.log(3) }) })
console.log(1) ```
```js setTimeout(() => { Promise.resolve().then(() => { console.log(4) }) process.nextTick(() => { console.log(3) }) console.log(2) }, 0)
setTimeout(() => { console.log(5) }, 0)
console.log(1) ```
```js const fs = require('fs')
fs.readFile(__filename, () => { console.log(3) Promise.resolve().then(() => { console.log(3) }) })
fs.readFile(__filename, () => { console.log(4) Promise.resolve().then(() => { console.log(4) }) })
setTimeout(() => { console.log(2) }, 0)
console.log(1)
// 输出结果有2种,取决于哪个异步IO事件先被系统处理完 // 124433 // 123344 ```
setImmediate
回调,同理有微任务,优先执行微任务。```js setImmediate(() => { Promise.resolve().then(() => { console.log(4) }) process.nextTick(() => { console.log(3) }) console.log(2) })
setImmediate(() => { console.log(5) }) console.log(1) ```
```js process.on('exit', () => { Promise.resolve().then(() => { console.log(4) }) console.log(2) })
process.on('exit', () => { Promise.resolve().then(() => { console.log(5) }) console.log(3) })
console.log(1) ```
本节主要介绍了 Node.js 中事件循环的工作原理,以及各个阶段的执行顺序和执行规则。
这个是很有必要掌握的知识,因为它是 Node.js 异步编程的基础,也是理解 Node.js 的关键。
掌握其原理,从而更从容的使用 Node.js。
如果笔者的介绍还是不能让你清晰的理解,可以进一步阅读下面的推荐材料。
文中部分配图来自于下面的参考文章。